package shaka.utils;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;

import shaka.utils.face.ICallBack1;
import shaka.utils.face.ICallBack2;

/**
 * <p>
 * Shaka 的工具箱
 * </p>
 * 
 * @author Shaka
 * @version Mar 25, 2013
 * @see
 * @since
 */
public class SkUtils {
	private static Map<String, SimpleDateFormat> formatM = new HashMap<String, SimpleDateFormat>();

	/**
	 * 讀取檔案
	 * 
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static String fileRead(File path) throws IOException {
		return FileUtils.readFileToString(path);
	}

	/**
	 * 讀取檔案
	 * 
	 * @param path
	 * @param encoding
	 * @return
	 * @throws IOException
	 */
	public static String fileRead(File path, String encoding) throws IOException {
		return FileUtils.readFileToString(path, encoding);
	}

	/**
	 * 寫入檔案
	 * 
	 * @param path
	 * @param text
	 * @throws IOException
	 */
	public static void fileWrite(File path, String text) throws IOException {
		FileUtils.writeStringToFile(path, text);
	}

	/**
	 * 寫入檔案
	 * 
	 * @param path
	 * @param text
	 * @param encoding
	 * @throws IOException
	 */
	public static void fileWrite(File path, String text, String encoding) throws IOException {
		FileUtils.writeStringToFile(path, text, encoding);
	}

	/**
	 * 讀取檔案
	 * 
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static String fileRead(String path) throws IOException {
		return FileUtils.readFileToString(new File(path));
	}

	/**
	 * 讀取檔案
	 * 
	 * @param path
	 * @param encoding
	 * @return
	 * @throws IOException
	 */
	public static String fileRead(String path, String encoding) throws IOException {
		return FileUtils.readFileToString(new File(path), encoding);
	}

	/**
	 * 寫入檔案
	 * 
	 * @param path
	 * @param text
	 * @throws IOException
	 */
	public static void fileWrite(String path, String text) throws IOException {
		FileUtils.writeStringToFile(new File(path), text);
	}

	/**
	 * 寫入檔案
	 * 
	 * @param path
	 * @param text
	 * @param encoding
	 * @throws IOException
	 */
	public static void fileWrite(String path, String text, String encoding) throws IOException {
		FileUtils.writeStringToFile(new File(path), text, encoding);
	}

	/**
	 * 相對檔案複製(樣版用)
	 * 
	 * @param srcFs
	 *            可用 File 或是 String
	 * @param targetFs
	 *            可用 File 或是 String
	 * @param pkgMapping
	 * @param fileMapping
	 * @param cb
	 */
	public static void fileTmplRelativelyCopy(List<?> srcFs, List<?> targetFs, Map<String, String> pkgMapping,
			Map<String, String> fileMapping, ICallBack2<File, File> cb) {
		if (srcFs.size() != targetFs.size()) throw new RuntimeException("src & target size does not match ...");
		for (int i = 0; i < srcFs.size(); i++) {
			File srcF = null;
			File targetF = null;
			Object o1 = srcFs.get(i);
			Object o2 = targetFs.get(i);
			if (o1 instanceof File) {
				srcF = (File) o1;
			}
			if (o1 instanceof String) {
				srcF = new File((String) o1);
			}
			if (o2 instanceof File) {
				targetF = (File) o2;
			}
			if (o2 instanceof String) {
				targetF = new File((String) o2);
			}
			if (srcF == null || targetF == null) { throw new RuntimeException("unknow type"); }
			fileTmplRelativelyCopy(srcF, targetF, pkgMapping, fileMapping, cb);
		}
	}

	/**
	 * 相對檔案複製(樣版用)
	 * 
	 * @param srcPath
	 * @param targetPath
	 * @param pkgMapping
	 * @param fileMapping
	 * @param cb
	 */
	public static void fileTmplRelativelyCopy(String srcPath, String targetPath, Map<String, String> pkgMapping,
			Map<String, String> fileMapping, ICallBack2<File, File> cb) {
		fileTmplRelativelyCopy(new File(srcPath), new File(targetPath), pkgMapping, fileMapping, cb);
	}

	/**
	 * 相對檔案複製(樣版用)
	 * 
	 * @param srcPath
	 * @param targetPath
	 * @param pkgMapping
	 * @param fileMapping
	 * @param cb
	 */
	@SuppressWarnings("rawtypes")
	public static void fileTmplRelativelyCopy(File srcF, File targetF, Map<String, String> pkgMapping,
			Map<String, String> fileMapping, ICallBack2<File, File> cb) {
		String srcFN = srcF.getName();
		String targetFn = null;
		if (pkgMapping != null) {
			targetFn = srcFN;
			for (Map.Entry<String, String> entry : pkgMapping.entrySet()) {
				targetFn = targetFn.replace(entry.getKey(), entry.getValue());
			}
		}
		if (targetFn == null) {
			targetFn = srcFN;
		}

		Iterator iter = FileUtils.iterateFiles(srcF, null, true);
		while (iter.hasNext()) {
			File f = (File) iter.next();

			// 建立路徑
			ArrayList<File> fs = new ArrayList<File>();
			for (File p = f.getParentFile(); !p.getName().equals(srcFN); p = p.getParentFile()) {
				fs.add(p);
			}
			Collections.reverse(fs);
			File outF = new File(targetF, targetFn);
			for (File p : fs) {
				// path name replace
				String pathName = p.getName();
				if (pkgMapping != null) {
					for (Map.Entry<String, String> entry : pkgMapping.entrySet()) {
						pathName = pathName.replace(entry.getKey(), entry.getValue());
					}
				}
				outF = new File(outF, pathName);
			}
			outF.mkdirs();

			// file name replace
			String fileName = f.getName();
			if (fileMapping != null) {
				for (Map.Entry<String, String> entry : fileMapping.entrySet()) {
					fileName = fileName.replace(entry.getKey(), entry.getValue());
				}
			}
			outF = new File(outF, fileName);

			cb.cb(f, outF);
		}
	}

	/**
	 * 載入 properties
	 * 
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static Properties propsLoad(String path) throws IOException {
		Properties prop = new Properties();
		FileReader reader = null;
		try {
			reader = new FileReader(path);
			prop.load(reader);
		} finally {
			if (reader != null) reader.close();
		}
		return prop;
	}

	public static void strEachLine(String src, ICallBack1<String> cb) {
		for (String line : src.split("\n")) {
			cb.cb(parseLine(line));
		}
	}

	/**
	 * 將字串內容轉換成 list
	 * 
	 * @param strList
	 * @return
	 */
	public static List<String> str2List(String strList) {
		List<String> list = new ArrayList<String>();
		for (String line : strList.split("\n")) {
			line = parseLine(line);
			if (line.matches("[^-/]+.*")) list.add(line);
		}
		return list;
	}

	/**
	 * 將下例的字串轉換成 Map (字串自動 trim)
	 * 
	 * <pre>
	 * k1 : value1
	 * k2 : value2
	 * </pre>
	 * 
	 * @param strMap
	 * @return
	 */
	public static Map<String, String> str2Map(String strMap) {
		Map<String, String> map = new LinkedHashMap<String, String>();
		Pattern pattern = Pattern.compile("([^:]+):([^:]+)");
		for (String line : strMap.split("\n")) {
			line = parseLine(line);
			Matcher matcher = pattern.matcher(line);
			String key = matcher.replaceAll("$1").trim();
			String value = matcher.replaceAll("$2").trim();
			map.put(key, value);
		}
		return map;
	}

	/**
	 * 第1個字母轉大寫
	 * 
	 * @param str
	 * @return
	 */
	public static String strUpper1(String str) {
		char[] cs = str.toCharArray();
		cs[0] = Character.toUpperCase(cs[0]);
		return new String(cs);
	}

	/**
	 * 取得 char (接受負數)
	 * 
	 * @param str
	 * @param idx
	 * @return
	 */
	public static char strCharAt(String str, int idx) {
		if (idx < 0) {
			idx = str.length() + idx;
		}
		return str.charAt(idx);
	}

	/**
	 * 文字切片 (接受負數)
	 * 
	 * @param str
	 * @param start
	 * @return
	 */
	public static String strSlice(String str, int start) {
		if (start < 0) {
			start = str.length() + start;
		}
		return str.substring(start);
	}

	/**
	 * 文字切片 (接受負數)
	 * 
	 * @param str
	 * @param start
	 * @param end
	 * @return
	 */
	public static String strSlice(String str, int start, int end) {
		if (start < 0) {
			start = str.length() + start;
		}
		if (end < 0) {
			end = str.length() + end;
		}
		return str.substring(start, end);
	}

	/**
	 * 將 \r 置換
	 * 
	 * @param line
	 * @return
	 */
	private static String parseLine(String line) {
		String num;
		if (line.charAt(line.length() - 1) != (char) 13) {
			num = line;
		} else {
			num = line.substring(0, line.length() - 1);
		}
		return num;
	}

	/**
	 * 樣版轉換 - 將 tmpl 中的 el 內容依 map 設置內容進行 置換
	 * 
	 * @param map
	 *            el 參數
	 * @param tmpl
	 *            使用 el 語法樣版
	 */
	public static void strTemplate(Map<String, String> map, StringBuffer tmpl) {
		strTemplate(map, tmpl, true);
	}

	/**
	 * 樣版轉換 - 將 tmpl 中的 關鍵字 內容依 map 設置內容進行 置換
	 * 
	 * @param map
	 *            關鍵字
	 * @param tmpl
	 *            樣版
	 * @param useEL
	 *            是否使用 el 語法
	 */
	public static void strTemplate(Map<String, String> map, StringBuffer tmpl, boolean useEL) {
		String tmplStr = tmpl.toString();
		for (Map.Entry<String, String> entry : map.entrySet()) {
			String str;
			if (useEL) {
				str = String.format("\\$\\{%s\\}", entry.getKey());
			} else {
				str = entry.getKey();
			}
			tmplStr = tmplStr.replaceAll(str, entry.getValue());
		}
		tmpl.setLength(0);
		tmpl.append(tmplStr);
	}

	/**
	 * 取得 date format
	 * 
	 * @param format
	 * @return
	 */
	private static SimpleDateFormat getDtFormat(String format) {
		SimpleDateFormat _format = formatM.get(format);
		if (_format == null) {
			_format = new SimpleDateFormat(format);
			// _format.setLenient(false); // 關閉寬容模式
			formatM.put(format, _format);
		}
		return _format;
	}

	/**
	 * 日期轉換
	 * 
	 * @param format
	 * @param date
	 * @return
	 */
	public static String dateParse(String format, Date date) {
		SimpleDateFormat _format = getDtFormat(format);
		synchronized (_format) {
			return _format.format(date);
		}
	}

	/**
	 * 日期轉換
	 * 
	 * @param format
	 * @param date
	 * @return
	 * @throws ParseException
	 */
	public static Date dateParse(String format, String date) throws ParseException {
		SimpleDateFormat _format = getDtFormat(format);
		synchronized (_format) {
			return _format.parse(date);
		}
	}

	/**
	 * 日期驗證
	 * 
	 * @param format
	 * @param date
	 * @return
	 */
	public static boolean dateValidate(String format, String date) {
		SimpleDateFormat _format = getDtFormat(format);
		try {
			synchronized (_format) {
				_format.parse(date);
			}
			return true;
		} catch (Exception e) {
			return false;
		}
	}

	@SuppressWarnings("rawtypes")
	private static Field getField(Object bean, String fieldName) {
		Field field = null;
		Class c = bean.getClass();
		while (c != Object.class) {
			try {
				field = c.getDeclaredField(fieldName);
			} catch (NoSuchFieldException e) {
			}
			if (field != null) break;
			c = c.getSuperclass();
		}
		if (field == null) { throw new RuntimeException(String.format("unknow field[%s] of obj[%s]", bean, fieldName)); }
		return field;
	}

	/**
	 * 設置 field, 支援 super class, 可指定 private 的 field
	 * 
	 * @param bean
	 * @param fieldName
	 * @param value
	 */
	public static void field(Object bean, String fieldName, Object value) {
		Field field = getField(bean, fieldName);
		field.setAccessible(true);
		try {
			field.set(bean, value);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 取得 field
	 * 
	 * @param bean
	 * @param fieldName
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T field(Object bean, String fieldName) {
		try {
			Field field = getField(bean, fieldName);
			field.setAccessible(true);
			return (T) field.get(bean);
		} catch (SecurityException e) {
			throw new RuntimeException(String.format("unknow field[%s] of obj[%s]", bean, fieldName));
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(String.format("unknow field[%s] of obj[%s]", bean, fieldName));
		} catch (IllegalAccessException e) {
			throw new RuntimeException(String.format("unknow field[%s] of obj[%s]", bean, fieldName));
		}
	}

	/**
	 * 建立物件
	 * 
	 * @param clazz
	 * @param args
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T newInstance(Class<T> clazz, Object... args) {
		if (args == null) {
			args = new Object[] {
				null
			};
		}
		if (args.length == 0) {
			try {
				return clazz.newInstance();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		Constructor<T>[] cs = (Constructor<T>[]) clazz.getConstructors();
		// 依 type 相同呼叫
		outer: for (Constructor<T> c : cs) {
			Type[] types = c.getGenericParameterTypes();
			if (types.length != args.length) continue;
			for (int i = 0; i < types.length; i++) {
				if (args[i] != null && types[i] != args[i].getClass()) {
					continue outer;
				}
			}
			try {
				return c.newInstance(args);
			} catch (Exception e) {
				// Do nothing ...
			}
		}
		// 依數傳入數量呼叫
		for (Constructor<T> c : cs) {
			Type[] types = c.getGenericParameterTypes();
			if (types.length != args.length) continue;
			try {
				return c.newInstance(args);
			} catch (Exception e) {
				// Do nothing ...
			}
		}
		throw new RuntimeException(String.format("Can't find constructor ... [%s], [%s]", clazz, Arrays.toString(args)));
	}

	/**
	 * 呼叫 method
	 * 
	 * @param bean
	 * @param methodName
	 * @param args
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static Object methodInvoke(Object bean, String methodName, Object... args) {
		try {
			if (args == null) {
				args = new Object[] {
					null
				};
			}
			Class clazz = bean.getClass();
			// clazz.getm
			boolean hasNull = false;
			Class[] types = new Class[args.length];
			for (int i = 0; i < args.length; i++) {
				if (args[i] == null) {
					hasNull = true;
					break;
				}
				types[i] = args[i].getClass();
			}
			// 依 type 呼叫
			if (!hasNull) {
				Method method = null;
				Class _clazz = clazz;
				do {
					try {
						method = _clazz.getDeclaredMethod(methodName, types);
					} catch (NoSuchMethodException e) {
						// 找不到 method
						_clazz = _clazz.getSuperclass();
					}
				} while (_clazz != null && method == null);
				if (method != null) { return method.invoke(bean, args); }
			}
			// 依 method name & 傳入參數個數
			Class _clazz = clazz;
			do {
				Method[] methods = _clazz.getDeclaredMethods();
				for (Method m : methods) {
					if (!m.getName().equals(methodName)) continue;
					if (m.getGenericParameterTypes().length != args.length) continue;
					try {
						return m.invoke(bean, args);
					} catch (IllegalArgumentException e) {
						// 傳入參數個數或型態不附, 不處理
					}
				}
				_clazz = _clazz.getSuperclass();
			} while (_clazz != null);
			throw new RuntimeException(String.format("unknow method class:[%s] method:[%s] args:[%s]", bean.getClass(),
					methodName, Arrays.toString(args)));
		} catch (RuntimeException e) {
			throw e;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 呼叫 setter
	 * 
	 * @param bean
	 * @param propertyName
	 * @param value
	 */
	public static void prop(Object bean, String propertyName, Object value) {
		propertyName = "set" + strUpper1(propertyName);
		methodInvoke(bean, propertyName, value);
	}

	/**
	 * 呼叫 getter
	 * 
	 * @param bean
	 * @param propertyName
	 * @return
	 */
	public static Object prop(Object bean, String propertyName) {
		propertyName = "get" + strUpper1(propertyName);
		return methodInvoke(bean, propertyName);
	}

}
